package org.pushforward.iwillsurvive.data;

import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.LinkedHashSet;

import org.pushforward.iwillsurvive.data.TypeValuePair.PairTypeEnum;
import org.pushforward.iwillsurvive.helpers.Utils;

/**
 * @author Sabre Runner
 *
 */
public class Post
{
	enum PostType {TASK, TEAM};
	
	DatabaseManager db = DatabaseManager.getInstance();
	
	private int id = 0; 						// a number
	private String email = null;				// a plain string (should be email validated)
	private PostType type = null;				// task or team (false or true)
	private double[] geoTag = new double[2];	// two doubles, also a "double double" string
	private Timestamp time = null;				// a timestamp of event start time
	private Timestamp timeLimit = null;			// a timestamp of event end time
	private int capacity = 0;					// a number
	private LinkedHashSet<String> abilities = new LinkedHashSet<String>(); 	// a set of strings, also "string^string^..."
	private int usersLimit = 0;												// a number
	private LinkedHashSet<String> users = new LinkedHashSet<String>();		// a set of strings, also "string^string^..."
	private StringBuilder content = new StringBuilder(1023);				// a string builder for easy addition

	/**
	 * This constructor receives the post id and reloads it from database
	 * @param id the post's unique identifier
	 * @throws SQLException 
	 */
	public Post(int id) throws SQLException
	{
		Utils.Log("Creating post from id " + Integer.toString(id));
		String[] queryPostStatement = {"select * from posts where id=?;"};
		TypeValuePair[][] parameterPair = {{new TypeValuePair(PairTypeEnum.INT, new Integer(id))}};
		StringBuilder resultSet = new StringBuilder();
		SQLException exception = new SQLException("Error reading post from database.");
		
		if (!db.ExecuteStatements(queryPostStatement, parameterPair, resultSet))
		{
			throw exception;
		}
		
		String[] postFields = resultSet.toString().trim()/*replaceAll("\\n", "")*/.split(",");
		if ((postFields.length != 11) || (id != Integer.parseInt(postFields[0])))
		{
			throw exception;
		}
		
		BuildPostByStrings(postFields);
	}

	/**
	 * This constructor takes all the arguments for the post and will
	 * 	make it from scratch using these parameters 
	 * @param email the user who created this post - must not be null
	 * @param type the type of post (0 = TASK, 1 = TEAM) - must not be null
	 * @param geoTag the post location on the map - must not be null
	 * @param time the time the post was made - must not be null
	 * @param timeLimit the time limit on this post (in seconds) - must not be null, may be 0
	 * @param capacity the post capacity (evacuation requirement or evacuation capability) - must not be null
	 * @param abilities the list of abilities (for team only) as a \n seperated string - must not be null
	 * @param usersLimit the maximum number of permitted registrants to this post - must not be null, may be 0
	 * @param content any extra notes - may be null
	 * @throws SQLDataException 
	 */
	public Post(String email, PostType type, double[] geoTag,
		 Date time, long timeLimit, int capacity,
		 String abilities, int usersLimit, String users, 
		 String content) throws SQLException
	{
		this.id = db.NextID() + 1;
		BuildPostByFields(email, type, geoTag, time, timeLimit, 
				  capacity, abilities, usersLimit, users, content);

		SavePost();
	}

	public String CreatePointString(double[] latlng)
	{
		return Double.toString(latlng[0]) + " " + Double.toString(latlng[1]);
	}
	
	/**
	 * @throws SQLException
	 */
	private void SavePost() throws SQLException
	{
		Utils.Log("Saving post " + Integer.toString(id));
		String[] insertStatement = {"insert into posts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"};
		TypeValuePair[][] insertPairs = {{new TypeValuePair(PairTypeEnum.INT, new Integer(this.id)),
										  new TypeValuePair(PairTypeEnum.STRING, this.email),
										  new TypeValuePair(PairTypeEnum.BOOLEAN, (this.type == PostType.TASK) ?
												  									new Boolean(false) :
												  										new Boolean(true)),
										  new TypeValuePair(PairTypeEnum.STRING, CreatePointString(geoTag)),
										  new TypeValuePair(PairTypeEnum.TIMESTAMP, this.time),
										  new TypeValuePair(PairTypeEnum.TIMESTAMP, this.timeLimit),
										  new TypeValuePair(PairTypeEnum.INT, new Integer(this.capacity)),
										  new TypeValuePair(PairTypeEnum.STRING, PrintAbilities()),
										  new TypeValuePair(PairTypeEnum.INT, new Integer(this.usersLimit)),
										  new TypeValuePair(PairTypeEnum.STRING, PrintUsers()),
										  new TypeValuePair(PairTypeEnum.STRING, 
												  (this.content.length() == 0) ? 
														  "null" : 
															  this.content.toString())}};
		
		if (!db.ExecuteStatements(insertStatement, insertPairs))
		{
			throw new SQLException("Error entering post to database.");
		}
	}

	private boolean DeletePost()
	{
		Utils.Log("Deleting post " + Integer.toString(id));
		String[] deleteStatement = {"delete from posts where id=?;"};
		TypeValuePair[][] deletePairs = {{new TypeValuePair(PairTypeEnum.INT, new Integer(this.id))}};
		
		return db.ExecuteStatements(deleteStatement, deletePairs);
	}
	
	/**
	 * This constructor builds a post from scratch. Only for use by the get posts method
	 * @param id
	 * @param data.getEmail()
	 * @param type
	 * @param geoTag
	 * @param time
	 * @param timeLimit
	 * @param capacity
	 * @param abilities
	 * @param usersLimit
	 * @param users
	 * @param content
	 * @throws SQLException
	 */
	public Post(String[] postFields, boolean save) throws SQLException
	{
		BuildPostByStrings(postFields);
		
		if (save)
		{
			SavePost();
		}
	}
	
	/**
	 * @param postFields
	 */
	private void BuildPostByStrings(String[] postFields)
	{
		this.id = Integer.parseInt(postFields[0]);
		this.email = postFields[1];
		this.type = (new Boolean(postFields[2].equalsIgnoreCase("1")).booleanValue()) 
						? PostType.TEAM 
							: PostType.TASK;
		String[] longLat = postFields[3].split(" ");
		this.geoTag[0] = Double.valueOf(longLat[0]).doubleValue();
		this.geoTag[1] = Double.valueOf(longLat[1]).doubleValue();
		this.time = Timestamp.valueOf(postFields[4]);
		this.timeLimit = Timestamp.valueOf(postFields[5]);
		this.capacity = new Integer(postFields[6]).intValue();
		LoadAbilities(postFields[7]);
		this.usersLimit = new Integer(postFields[8]).intValue();
		LoadUsers(postFields[9]);
		this.content.append((postFields[10].equalsIgnoreCase("null")) ? "" : postFields[10]);
	}
	
	/**
	 * @param email
	 * @param type
	 * @param geoTag
	 * @param time
	 * @param timeLimit
	 * @param capacity
	 * @param abilities
	 * @param usersLimit
	 * @param users
	 * @param content
	 */
	private void BuildPostByFields(String email, PostType type, double[] geoTag, 
						   Date time, long timeLimit, int capacity, 
						   String abilities, int usersLimit, String users, String content)
	{
		this.email = email;
		this.type = type;
		this.geoTag = geoTag;
		this.time = new Timestamp(time.getTime());
		this.timeLimit = new Timestamp(time.getTime() + timeLimit * 1000);
		this.capacity = capacity;
		LoadAbilities(abilities);
		this.usersLimit = usersLimit;
		LoadUsers(users);
		this.content.append(content);
	}
	
	private String PrintUsers()
	{
		if (this.users.isEmpty())
		{
			return "null";
		}
		
		StringBuilder userBuilder = new StringBuilder();
		
		for (String user : this.users)
		{
			userBuilder.append(user).append("@");
		}
		
		userBuilder.setLength(userBuilder.length()-1);
		
		return userBuilder.toString();
	}

	private void LoadUsers(String users)
	{
		this.users.clear();
		
		if (users.isEmpty() || users.equalsIgnoreCase("null"))
		{
			return;
		}
		
		String[] usersStrings = users.split("@");

		for (int index = 0; index < usersStrings.length; index++)
		{
			this.users.add(usersStrings[index]);
		}
	}

	private String PrintAbilities()
	{
		if (this.abilities.isEmpty())
		{
			return "null";
		}
		
		StringBuilder abilitiesStringBuilder = new StringBuilder();
		
		for (String ability : this.abilities)
		{
			abilitiesStringBuilder.append(ability).append("@");
		}
		
		abilitiesStringBuilder.setLength(abilitiesStringBuilder.length() - 1);
		
		return abilitiesStringBuilder.toString();
	}

	private void LoadAbilities(String abilitiesList)
	{
		this.abilities.clear();
		
		if (abilitiesList.isEmpty() || abilitiesList.equalsIgnoreCase("null"))
		{
			return;
		}
		
		String[] abilitieStrings = abilitiesList.split("@");
		
		for (String ability : abilitieStrings)
		{
			this.abilities.add(ability);
		}
	}

	public int Id()
	{
		return this.id;
	}
	public String Email()
	{
		return this.email;
	}
	public PostType Type()
	{
		return this.type;
	}
	public double[] GeoTag()
	{
		return this.geoTag;
	}
	public Date Time()
	{
		return new Date(this.time.getTime());
	}
	public long TimeLimit()
	{
		return this.timeLimit.getTime() - this.time.getTime();
	}
	public void SetTimeLimit(long timeLimit)
	{
		this.timeLimit = new Timestamp(time.getTime() + timeLimit * 1000);
	}
	public int Capacity()
	{
		return this.capacity;
	}
	public void SetCapacity(int capacity)
	{
		this.capacity = capacity;
	}
	public LinkedHashSet<String> Abilities()
	{
		return this.abilities;
	}
	public void SetAbilities(String abilities)
	{
		LoadAbilities(abilities);
	}
	/**
	 * Adds the given ability to this post</br>
	 * Assumes that each ability has only one description. Meaning no "Heavy Lifting" and "heavy lifting"
	 * @param ability the ability to add
	 * @return false if already exists, true if successful
	 */
	public boolean AddAbility(String ability)
	{
		if (this.abilities.contains(ability))
		{
			return false;
		}
		this.abilities.add(ability);
		return true;
	}
	/**
	 * Removes given ability from abilities list
	 * Assumes that each ability has only one description. Meaning no "Heavy Lifting" and "heavy lifting" 
	 * @param ability the ability to remove
	 * @return false if the ability is not in the list, true if successful
	 */
	public boolean RemoveAbility(String ability)
	{
		if (!this.abilities.contains(ability))
		{
			return false;
		}
		this.abilities.remove(ability);
		return true;
	}
	public int UsersLimit()
	{
		return this.usersLimit;
	}
	public void SetUsersLimit(int usersLimit)
	{
		this.usersLimit = usersLimit;
	}
	public void SetUsers(String users)
	{
		LoadUsers(users);
	}
	/**
	 * Adds the given user to this post</br>
	 * @param user the user to add
	 * @return false if already exists, true if successful
	 */
	public boolean AddUser(String user)
	{
		if (this.users.contains(user))
		{
			return false;
		}
		this.users.add(user);
		
		if (DeletePost())
		{
			try
			{
				SavePost();
			} catch (SQLException e)
			{
				return false;
			}
		}
		return true;
	}
	/**
	 * Removes given user from abilities list
	 * @param user the user to remove
	 * @return false if the user is not in the list, true if successful
	 */
	public boolean RemoveUser(String user)
	{
		if (!this.users.contains(user))
		{
			return false;
		}
		this.users.remove(user);
		
		if (DeletePost())
		{
			try
			{
				SavePost();
			} catch (SQLException e)
			{
				return false;
			}
		}
		return true;
	}
	public String Content()
	{
		return this.content.toString();
	}
	public void SetContent(String content)
	{
		this.content.setLength(0);
		this.content.append(content);
	}

	/**
	 * Returns a comma separated, EOL terminated string representing this post
	 */
	@Override
	public String toString()
	{
		StringBuilder postBuilder = new StringBuilder();
		
		postBuilder.append(this.id).append(",");
		postBuilder.append(this.email).append(",");
		postBuilder.append((this.type == PostType.TASK) ? 0 : 1).append(",");
		postBuilder.append(CreatePointString(geoTag)).append(",");
		postBuilder.append(this.time.getTime()).append(",");
		postBuilder.append(this.timeLimit.getTime()).append(",");
		postBuilder.append(this.capacity).append(",");
		postBuilder.append(PrintAbilities()).append(",");
		postBuilder.append(this.usersLimit).append(",");
		postBuilder.append(PrintUsers()).append(",");
		postBuilder.append(this.content).append("\n");

		return postBuilder.toString();
	}

	@Override
	public boolean equals(Object other)
	{
		Post otherPost = (Post)other;
		return (this.id == otherPost.id);
	}

	@Override
	public int hashCode()
	{
		return this.id;
	}
}
